﻿/*
 *  date: 2018-03-30
 *  author: John-chen
 *  cn: 资源加载器
 *  en: todo:
 */

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Object = UnityEngine.Object;

namespace JyFramework
{
    /// <summary>
    /// 资源加载器
    /// </summary>
    public class ResLoader : IResLoadFunction
    {
        public ResLoader()
        {
            _assetBundles = new Dictionary<string, AssetBundle>();
            //_fileListPath = PathHelper.Ins.AssetBundlePath + "file.txt";
            _assetBundlePath = PathHelper.Ins.AssetBundlePath + "AssetBundle";
            _asyncLoadingPaths = new List<string>();

            _eventCtrl = new EventController("ResLoader");
            MessageCtrl.Ins.AddEventCtrl(_eventCtrl);
            _eventCtrl.RegistEvent(ResEvent.DestoryRefAssetBundle, OnDestoryAssetBundle);

            ReadMainFest();
            ReadFileList();
        }

        public void Remove()
        {
            MessageCtrl.Ins.RemoveCtrl(_eventCtrl);
        }

        /// <summary>
        /// 同步加载
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="path"> 传入的是ResourceCore 下的路径 </param>
        /// <returns></returns>
        public T LoadRes<T>(string path) where T : Object
        {
            string newPath = /*"resourcescore/" +*/ path;
            newPath = newPath.ToLower();
            string name = PathHelper.Ins.GetFileName(newPath);

            AssetBundle ab = null;
            if (_assetBundles.TryGetValue(newPath, out ab))
            {
                return ab.LoadAsset<T>(name);
            }

            ab = DependenciesLoad(newPath);
            AddResToCtrl(path, ab);

            return ab.LoadAsset<T>(name);
        }

        /// <summary>
        /// 异步返回值加载资源
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="yieldArgs"></param>
        /// <returns></returns>
        public T LoadRes<T>(YieldArgs yieldArgs) where T : Object
        {
            string path = yieldArgs.Path;
            string name = PathHelper.Ins.GetFileName(path.ToLower());

            AssetBundle ab = yieldArgs.Obj as AssetBundle;

            return ab.LoadAsset<T>(name);
        }

        /// <summary>
        /// 实例化
        /// </summary>
        /// <param name="yieldArgs"></param>
        /// <returns></returns>
        public GameObject Instantiate(YieldArgs yieldArgs)
        {
            string name = PathHelper.Ins.GetFileName(yieldArgs.Path);
            AssetBundle ab = yieldArgs.Obj as AssetBundle;

            GameObject obj = ab.LoadAsset<GameObject>(name);
            var newObj = GameObject.Instantiate(obj);
            yieldArgs.gameObject = newObj;

            newObj.AddComponent<RefObjHandle>().SetResName(yieldArgs.Path);

            return newObj;
        }

        /// <summary>
        /// 协程异步加载资源
        /// </summary>
        /// <param name="yieldArgs"></param>
        /// <returns></returns>
        public IEnumerator AsyncLoadRes(YieldArgs yieldArgs, AsyncLoadedHandle action)
        {
            string path = yieldArgs.Path;

            AssetBundle ab = null;
            if (_assetBundles.TryGetValue(path, out ab))
            {
                yieldArgs.LoadDone(ab);
                _asyncLoadingPaths.Remove(path);
                action?.Invoke(ab);
                yield break;
            }

            //---------------------------------------
            // 用来关闭多次重复加载的调用
            if (_asyncLoadingPaths.Contains(path))
                yield break;
            else
            {
                _asyncLoadingPaths.Add(path);
            }
            //---------------------------------------

            var myAsync = AsyncDependenciesLoad(yieldArgs);
            while (!yieldArgs.IsDone)
            {
                yield return myAsync;
            }

            ab = (AssetBundle) yieldArgs.Obj;
            //---------------------------------------
            // 加载成功移除管理列表,可以再次调用
            _asyncLoadingPaths.Remove(path);
            //---------------------------------------
            AddResToCtrl(path, ab);

            // 加载成功后的回调
            action?.Invoke(ab);
        }

        /// <summary>
        /// 异步实例化
        /// </summary>
        /// <param name="yieldArgs"></param>
        /// <param name="action"></param>
        /// <returns></returns>
        public IEnumerator AsyncInstantiate(YieldArgs yieldArgs, AsyncInstantiateHandler action)
        {
            GameObject obj = null;
            if (yieldArgs.gameObject)
            {
                obj = Instantiate(yieldArgs);
                action?.Invoke(obj);
                yield break;
            }

            while (!yieldArgs.IsDone)
            {
                yield return yieldArgs;
            }

            obj = Instantiate(yieldArgs);
            action?.Invoke(obj);
        }

        /// <summary>
        /// 读取 AssetBundleManifest
        /// </summary>
        private void ReadMainFest()
        {
            _maniAssetBundle = AssetBundle.LoadFromFile(_assetBundlePath);
            if (_maniAssetBundle != null)
            {
                _manifest = _maniAssetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
            }
        }

        private void ReadFileList()
        {
            //_fileList = FileHelper.Ins.ReadFileText(_fileListPath);
        }


        /// <summary>
        /// 依赖加载
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private AssetBundle DependenciesLoad(string path)
        {
            AssetBundle ab = null;
            if (_assetBundles.TryGetValue(path, out ab))
            {
                return ab;
            }

            var filePaths = _manifest.GetAllDependencies(path);
            int fileCnt = filePaths.Length;
            if (fileCnt == 0)
            {
                string newPath = PathHelper.Ins.AssetBundlePath + path;
                ab = AssetBundle.LoadFromFile(newPath);

                if (!_assetBundles.ContainsKey(path) && ab != null)
                {
                    _assetBundles.Add(path, ab);
                }
                else
                {
                    Debug.LogError(string.Format("The {0} assetbundle is null", path));
                }

                return ab;
            }
            else if (fileCnt > 0)
            {
                for (int i = 0; i < fileCnt; i++)
                {
                    string newFilePath = filePaths[i];
                    var newAb = DependenciesLoad(newFilePath);
                    if (!_assetBundles.ContainsKey(newFilePath) && newAb != null)
                    {
                        _assetBundles.Add(newFilePath, newAb);
                    }
                }
                if (!_assetBundles.ContainsKey(path))
                {
                    string myPath = PathHelper.Ins.AssetBundlePath + path;
                    var myAb = AssetBundle.LoadFromFile(myPath);
                    if (myAb != null)
                    {
                        _assetBundles.Add(path, myAb);
                    }
                    else
                    {
                        Debug.LogError(string.Format("The {0} assetbundle is null", myPath));
                    }
                }

            }

            return _assetBundles.ContainsKey(path) ? _assetBundles[path] : null;
        }

        /// <summary>
        /// 异步依赖加载资源
        /// </summary>
        /// <param name="yieldArgs"></param>
        /// <returns></returns>
        private IEnumerator AsyncDependenciesLoad(YieldArgs yieldArgs)
        {
            string path = yieldArgs.Path;

            AssetBundle ab = null;
            if (_assetBundles.TryGetValue(path, out ab))
            {
                yieldArgs.LoadDone(ab);
                yield break;
            }

            var filePaths = _manifest.GetAllDependencies(path);
            int fileCnt = filePaths.Length;
            if (fileCnt == 0)
            {
                string newPath = PathHelper.Ins.AssetBundlePath + path;
                var abReq = AssetBundle.LoadFromFileAsync(newPath);
                while (!abReq.isDone)
                {
                    yield return abReq;
                }

                ab = abReq.assetBundle;
                yieldArgs.LoadDone(ab);

                if (!_assetBundles.ContainsKey(path) && ab != null)
                {
                    _assetBundles.Add(path, ab);
                }
                else
                {
                    Debug.LogError(string.Format("The {0} assetbundle is null", path));
                }
                yield break;
            }
            else if (fileCnt > 0)
            {
                List<YieldArgs> ys = new List<YieldArgs>();
                for (int i = 0; i < fileCnt; i++)
                {
                    string newFilePath = filePaths[i];
                    var ya = new YieldArgs(newFilePath);
                    var asyncDl = AsyncDependenciesLoad(ya);
                    yield return asyncDl;
                    ys.Add(ya);
                }

                if (!_assetBundles.ContainsKey(path))
                {
                    string myPath = PathHelper.Ins.AssetBundlePath + path;
                    for (int i = 0; i < fileCnt; i++)
                    {
                        var yg = ys[i];
                        while (!yg.IsDone)
                        {
                            yield return yg;
                        }
                    }

                    var myReq = AssetBundle.LoadFromFileAsync(myPath);

                    while (!myReq.isDone)
                    {
                        yield return myReq;
                    }

                    yieldArgs.LoadDone(myReq.assetBundle);
                    if (myReq.assetBundle)
                    {
                        _assetBundles.Add(path, myReq.assetBundle);
                    }
                    else
                    {
                        Debug.LogError(string.Format("The {0} assetbundle is null", myPath));
                    }
                    yield break;
                }
            }
        }

        /// <summary>
        /// 实例化对象协程
        /// </summary>
        /// <param name="yieldArgs"></param>
        /// <returns></returns>
        private IEnumerator InsObj(YieldArgs yieldArgs)
        {
            while (!yieldArgs.IsDone)
            {
                yield return yieldArgs;
            }

            string name = PathHelper.Ins.GetFileName(yieldArgs.Path);
            AssetBundle ab = yieldArgs.Obj as AssetBundle;
            var obj = ab.LoadAsset<GameObject>(name);

            GameObject.Instantiate(obj);
        }

        /// <summary>
        /// 将资源名和加载ab资源添加到管理中
        /// </summary>
        /// <param name="resName"></param>
        /// <param name="abRes"></param>
        private void AddResToCtrl(string resName, AssetBundle abRes)
        {
            _eventCtrl.NotifyEvent(ResEvent.AddAssetBundleRes, resName, abRes);
        }


        /// <summary>
        /// 删除AssetBundle
        /// </summary>
        /// <param name="args"></param>
        private void OnDestoryAssetBundle(object[] args)
        {
            string resName = (string) args[0];

            if(_assetBundles.ContainsKey(resName))
                _assetBundles.Remove(resName);
        }

        //private string _fileListPath;
        private string _assetBundlePath;
        private AssetBundle _maniAssetBundle;
        private AssetBundleManifest _manifest;
        //private Dictionary<string, JyFileInfo> _fileList;
        private Dictionary<string, AssetBundle> _assetBundles;

        // 异步加载中的资源路径
        private List<string> _asyncLoadingPaths;
        // 
        private EventController _eventCtrl;
    }
}